import 'dart:convert';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:lib_http/http/http_error.dart';
import 'package:lib_http/http/net_code.dart';

/// 请求错误统一处理
typedef HttpFormatError = Future<bool> Function(
    String tag, String code, String message);

/// http开始请求回调
typedef HttpStartCallback = void Function();

/// http请求成功回调
typedef HttpSuccessCallback<T> = void Function(dynamic data);

/// 失败回调
typedef HttpFailureCallback = void Function(HttpError error);

/// http请求完成回调
typedef HttpFinishCallback = void Function();

/// 网络请求管理工具，基于dio框架实现
class HttpManager {
  /// 超时时间
  static const Duration _connectTimeout = Duration(seconds: 8);
  static const Duration _receiveTimeout = Duration(seconds: 8);
  static const Duration _sendTimeout = Duration(seconds: 8);

  static const String _codeElement = "code";
  static const String _dataElement = "data";
  static const String _msgElement = "msg";

  String _successCode = "200";

  /// http request methods
  static const getMethod = 'GET';
  static const postMethod = 'POST';

  /// 同一个CancelToken可以用于多个请求，当一个CancelToken取消时，所有使用该CancelToken的请求都会被取消
  final Map<dynamic, CancelToken> _cancelTokens = <dynamic, CancelToken>{};

  /// Dio 对象
  late Dio _client;

  Dio get client => _client;

  /// 代理
  String proxyUrl = "";
  String proxyPort = "";

  /// 统一错误处理
  HttpFormatError? formatError;

  /// 单例
  factory HttpManager.getInstance() => _instance;

  static final HttpManager _instance = HttpManager._();

  HttpManager._() {
    _client = Dio();
  }

  /// 初始化公共属性
  ///
  /// [baseUrl] 地址前缀
  /// [connectTimeout] 连接超时时间
  /// [receiveTimeout] 接收超时时间
  /// [interceptors] 拦截器组
  /// [formatError] 统一错误处理
  /// [successCode] 请求成功码
  void init(String baseUrl,
      {Duration? connectTimeout = _connectTimeout,
      Duration receiveTimeout = _receiveTimeout,
      Duration sendTimeout = _sendTimeout,
      List<Interceptor>? interceptors,
      HttpFormatError? formatError,
      String? successCode,
      String code = _codeElement,
      String data = _dataElement,
      String msg = _msgElement}) {
    // 构建默认配置
    _client.options = _client.options.copyWith(
        baseUrl: baseUrl,
        connectTimeout: connectTimeout,
        receiveTimeout: receiveTimeout,
        sendTimeout: sendTimeout);

    if (interceptors != null && interceptors.isNotEmpty) {
      _client.interceptors.addAll(interceptors);
    }
    this.formatError = formatError;
    _successCode = successCode ?? _successCode;
  }

  /// 代理设置
  ///
  /// [proxyIp] 代理地址
  /// [proxyPort] 代理端口
  void proxySetting(String proxyUrl, String proxyPort) {
    this.proxyUrl = proxyUrl;
    this.proxyPort = proxyPort;
    var clientAdapter = _client.httpClientAdapter as IOHttpClientAdapter;
    clientAdapter.createHttpClient = () {
      var client = HttpClient();
      client.findProxy = (uri) => "PROXY $proxyUrl:$proxyPort";
      return client;
    };
  }

  /// 网络证书配置
  ///
  /// [ip] ip地址
  /// [port] 端口
  /// [certificate] 证书
  void validateCertificate(
      bool Function(X509Certificate? certificate, String ip, int port)
          validateCertificate) {
    var clientAdapter = _client.httpClientAdapter as IOHttpClientAdapter;
    clientAdapter.validateCertificate = validateCertificate;
  }

  /// 更新baseUrl
  ///
  /// [baseUrl] 地址前缀
  void updateBaseUrl(String baseUrl) {
    _client.options = _client.options.copyWith(baseUrl: baseUrl);
  }

  ///Get网络请求
  ///
  ///[path] 网络请求地址
  ///[params] url请求参数支持restful
  ///[tag] 请求统一标识，用于取消网络请求
  ///[options] 请求配置
  ///[startCallback] 请求开始回调
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[finishCallback] 请求结束回调
  ///[pretreatment] 是否预处理
  void get(String path,
      {Map<String, dynamic>? params,
      String? tag,
      Options? options,
      HttpStartCallback? startCallback,
      HttpSuccessCallback? successCallback,
      HttpFailureCallback? errorCallback,
      HttpFinishCallback? finishCallback,
      bool pretreatment = true}) async {
    _request(path, getMethod,
        params: params,
        tag: tag,
        options: options,
        startCallback: startCallback,
        successCallback: successCallback,
        errorCallback: errorCallback,
        finishCallback: finishCallback,
        pretreatment: pretreatment);
  }

  ///post网络请求
  ///
  ///[path] 网络请求地址
  ///[data] post请求参数
  ///[params] url请求参数支持restful
  ///[tag] 请求统一标识，用于取消网络请求
  ///[options] 请求配置
  ///[startCallback] 请求开始回调
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[finishCallback] 请求结束回调
  ///[pretreatment] 是否预处理
  void post(String path,
      {dynamic data,
      Map<String, dynamic>? params,
      String? tag,
      Options? options,
      HttpStartCallback? startCallback,
      HttpSuccessCallback? successCallback,
      HttpFailureCallback? errorCallback,
      HttpFinishCallback? finishCallback,
      bool pretreatment = true}) async {
    _request(path, postMethod,
        data: data,
        params: params,
        tag: tag,
        options: options,
        startCallback: startCallback,
        successCallback: successCallback,
        errorCallback: errorCallback,
        finishCallback: finishCallback,
        pretreatment: pretreatment);
  }

  /// 网络请求
  ///
  /// [path] 网络请求地址不包含域名
  /// [method] 网络类型
  /// [options] 请求配置
  /// [data] post请求参数
  /// [tag] 请求统一标识，可用于取消网络请求
  /// [params] url请求参数
  /// [startCallback] 请求开始回调
  /// [successCallback] 请求成功回调
  /// [errorCallback] 请求失败回调
  /// [finishCallback] 请求完成回调
  /// [pretreatment] 是否预处理
  void _request(String path, String method,
      {dynamic data,
      Map<String, dynamic>? params,
      String? tag,
      Options? options,
      HttpStartCallback? startCallback,
      HttpSuccessCallback? successCallback,
      HttpFailureCallback? errorCallback,
      HttpFinishCallback? finishCallback,
      bool pretreatment = true}) async {
    // 默认以url作为tag
    tag ??= path;
    // 检查网络状态
    bool connected = await _checkNetworkStatus();
    if (!connected) {
      bool intercept =
          await formatError?.call(tag, NetCode.unConnected, "") ?? false;
      if (intercept) {
        return;
      }
      if (errorCallback != null) {
        errorCallback(HttpError(NetCode.unConnected));
      }
      return;
    }
    // 开始请求回调
    if (startCallback != null) {
      startCallback();
    }
    // 默认以url为tag创建cancelToken保存
    CancelToken cancelToken;
    cancelToken = _cancelTokens[tag] ?? CancelToken();
    _cancelTokens[tag] = cancelToken;
    // 请求数据处理
    params = params ?? {};
    options?.method = method;
    options = options ?? Options(method: method);
    path = _restfulUrl(path, params);
    try {
      // 执行请求
      Response<dynamic> response = await _client.request(path,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);
      // 处理请求结果
      if (pretreatment) {
        // 请求结果预处理
        Map<String, dynamic> map;
        if (response.data is Map) {
          map = response.data;
        } else if (response.data is List<dynamic>) {
          map = {_codeElement: _successCode, _dataElement: response.data};
        } else {
          // 尝试json转化
          map = json.decode(response.data) ?? {};
        }
        String statusCode = "${map[_codeElement] ?? ""}";
        if (statusCode == _successCode) {
          if (successCallback != null) {
            successCallback(map[_dataElement]);
          }
        } else {
          String message = map[_msgElement];
          bool intercept =
              await formatError?.call(tag, statusCode, message) ?? false;
          if (intercept) {
            _cancelTokens.remove(tag);
          }
          if (errorCallback != null) {
            errorCallback(HttpError(statusCode, msg: message));
          }
        }
      } else {
        // 直接返回响应体
        if (successCallback != null) {
          successCallback(response.data);
        }
      }
    } on DioException catch (e, _) {
      var httpError = HttpError.dioError(e);
      bool intercept =
          await formatError?.call(tag, httpError.code, httpError.msg) ?? false;
      if (intercept) {
        _cancelTokens.remove(tag);
      }
      // 捕获到请求错误
      if (errorCallback != null) {
        errorCallback(httpError);
      }
    } catch (e, _) {
      bool intercept =
          await formatError?.call(tag, NetCode.unknown, e.toString()) ?? false;
      if (intercept) {
        _cancelTokens.remove(tag);
      }
      // 捕获到其他代码错误
      if (errorCallback != null) {
        errorCallback(HttpError(NetCode.unknown, msg: e.toString()));
      }
    } finally {
      if (finishCallback != null) {
        finishCallback();
      }
      // 移除cancelToken
      _cancelTokens.remove(tag);
    }
  }

  ///Get异步网络请求
  ///
  ///[path] 网络请求地址不包含域名
  ///[params] url请求参数支持restful
  ///[tag] 请求统一标识，用于取消网络请求
  ///[options] 请求配置
  ///[pretreatment] 是否预处理
  Future<dynamic> getAsync(
    String path, {
    Map<String, dynamic>? params,
    String? tag,
    Options? options,
    bool pretreatment = true,
  }) async {
    return _requestAsync(
      path,
      getMethod,
      params: params,
      tag: tag,
      options: options,
      pretreatment: pretreatment,
    );
  }

  ///post异步网络请求
  ///
  ///[path] 网络请求地址不包含域名
  ///[data] post请求参数
  ///[params] url请求参数支持restful
  ///[tag] 请求统一标识，用于取消网络请求
  ///[options] 请求配置
  ///[pretreatment] 是否预处理
  Future<dynamic> postAsync(
    String path, {
    dynamic data,
    Map<String, dynamic>? params,
    String? tag,
    Options? options,
    bool pretreatment = true,
  }) async {
    return _requestAsync(
      path,
      postMethod,
      data: data,
      params: params,
      tag: tag,
      options: options,
      pretreatment: pretreatment,
    );
  }

  /// 异步网络请求
  ///
  /// [path] 网络请求地址不包含域名
  /// [method] 网络类型
  /// [options] 请求配置
  /// [data] post请求参数
  /// [tag] 请求统一标识，可用于取消网络请求
  /// [params] url请求参数
  /// [pretreatment] 是否预处理
  Future<dynamic> _requestAsync(
    String path,
    String method, {
    dynamic data,
    Map<String, dynamic>? params,
    String? tag,
    Options? options,
    bool pretreatment = true,
  }) async {
    tag ??= path;
    bool connected = await _checkNetworkStatus();
    if (!connected) {
      formatError?.call(tag, NetCode.unConnected, "");
      return Future.value(HttpError(NetCode.unConnected));
    }
    // 请求数据处理
    params = params ?? {};
    options?.method = method;
    options = options ?? Options(method: method);
    path = _restfulUrl(path, params);
    try {
      // 执行请求
      Response<dynamic> response = await _client.request(path,
          data: data, queryParameters: params, options: options);
      if (pretreatment) {
        Map<String, dynamic> map;
        if (response.data is Map) {
          map = response.data;
        } else if (response.data is List<dynamic>) {
          map = {_codeElement: _successCode, _dataElement: response.data};
        } else {
          // 当后台返回的是string，尝试手动转成json
          map = json.decode(response.data) ?? {};
        }
        String statusCode = "${map[_codeElement] ?? ""}";
        if (statusCode == _successCode) {
          return Future.value(map[_dataElement]);
        } else {
          String message = map[_msgElement];
          formatError?.call(tag, statusCode, message);
          return Future.value(HttpError(statusCode, msg: message));
        }
      } else {
        return Future.value(response.data);
      }
    } on DioException catch (e, _) {
      var httpError = HttpError.dioError(e);
      formatError?.call(tag, httpError.code, httpError.msg);
      return Future.value(httpError);
    } catch (e, _) {
      formatError?.call(tag, NetCode.unknown, e.toString());
      return Future.value(HttpError(NetCode.unknown, msg: e.toString()));
    }
  }

  /// 下载文件
  ///
  /// [path] 网络请求地址不包含域名
  /// [savePath] 下载文件保留地址
  /// [data] post请求参数
  /// [params] url请求参数
  /// [tag] 请求统一标识，可用于取消网络请求
  /// [options] 请求配置
  /// [onReceiveProgress] 下载进度回调
  /// [successCallback] 请求成功回调
  /// [errorCallback] 请求失败回调
  void download(
    String path,
    String savePath, {
    dynamic data,
    Map<String, dynamic>? params,
    String? tag,
    Options? options,
    ProgressCallback? onReceiveProgress,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
  }) async {
    // 默认以url作为tag
    tag ??= path;
    // 检查网络状态
    bool connected = await _checkNetworkStatus();
    if (!connected) {
      bool intercept =
          await formatError?.call(tag, NetCode.unConnected, "") ?? false;
      if (intercept) {
        return;
      }
      if (errorCallback != null) {
        errorCallback(HttpError(NetCode.unConnected));
      }
      return;
    }
    // 默认以url为tag创建cancelToken保存
    CancelToken cancelToken;
    cancelToken = _cancelTokens[tag] ?? CancelToken();
    _cancelTokens[tag] = cancelToken;
    // 请求数据处理
    params = params ?? {};
    options = options ?? Options(sendTimeout: _sendTimeout);
    path = _restfulUrl(path, params);
    try {
      // 开始请求
      Response response = await _client.download(path, savePath,
          onReceiveProgress: onReceiveProgress,
          queryParameters: params,
          data: data,
          options: options,
          cancelToken: cancelToken);
      // 成功
      if (successCallback != null) {
        successCallback(response.data);
      }
    } on DioException catch (e, _) {
      var httpError = HttpError.dioError(e);
      bool intercept =
          await formatError?.call(tag, httpError.code, httpError.msg) ?? false;
      if (intercept) {
        _cancelTokens.remove(tag);
      }
      if (errorCallback != null) {
        errorCallback(httpError);
      }
    } catch (e, _) {
      bool intercept =
          await formatError?.call(tag, NetCode.unknown, e.toString()) ?? false;
      if (intercept) {
        _cancelTokens.remove(tag);
      }
      if (errorCallback != null) {
        errorCallback(HttpError(NetCode.unknown, msg: e.toString()));
      }
    } finally {
      // 移除cancelToken
      _cancelTokens.remove(tag);
    }
  }

  /// 上传文件
  ///
  /// [path] 网络请求地址不包含域名
  /// [data] post请求参数
  /// [params] url请求参数
  /// [tag] 请求统一标识，可用于取消网络请求
  /// [options] 请求配置
  /// [onSendProgress] 上传进度回调
  /// [successCallback] 请求成功回调
  /// [errorCallback] 请求失败回调
  void upload(String path,
      {dynamic data,
      Map<String, dynamic>? params,
      String? tag,
      Options? options,
      ProgressCallback? onSendProgress,
      HttpSuccessCallback? successCallback,
      HttpFailureCallback? errorCallback,
      bool pretreatment = true}) async {
    // 默认以url作为tag
    tag ??= path;
    // 检查网络状态
    bool connected = await _checkNetworkStatus();
    if (!connected) {
      bool intercept =
          await formatError?.call(tag, NetCode.unConnected, "") ?? false;
      if (intercept) {
        return;
      }
      if (errorCallback != null) {
        errorCallback(HttpError(NetCode.unConnected));
      }
      return;
    }
    // 默认以url为tag创建cancelToken保存
    CancelToken cancelToken;
    cancelToken = _cancelTokens[tag] ?? CancelToken();
    _cancelTokens[tag] = cancelToken;
    // 请求数据处理
    params = params ?? {};
    options = options ?? Options(method: postMethod);
    try {
      // 执行请求
      Response<dynamic> response = await _client.request(path,
          onSendProgress: onSendProgress,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);
      // 处理结果
      if (pretreatment) {
        Map<String, dynamic> map;
        if (response.data is Map) {
          map = response.data;
        } else if (response.data is List<dynamic>) {
          map = {_codeElement: _successCode, _dataElement: response.data};
        } else {
          // 当后台返回的是string，尝试手动转成json
          map = json.decode(response.data) ?? {};
        }
        String statusCode = "${map[_codeElement] ?? ""}";
        if (statusCode == _successCode) {
          if (successCallback != null) {
            successCallback(map[_dataElement]);
          }
        } else {
          String message = map[_msgElement];
          bool intercept =
              await formatError?.call(tag, statusCode, message) ?? false;
          if (intercept) {
            _cancelTokens.remove(tag);
          }
          if (errorCallback != null) {
            errorCallback(HttpError(statusCode, msg: message));
          }
        }
      } else {
        if (successCallback != null) {
          successCallback(response.data);
        }
      }
    } on DioException catch (e, _) {
      var httpError = HttpError.dioError(e);
      bool intercept =
          await formatError?.call(tag, httpError.code, httpError.msg) ?? false;
      if (intercept) {
        _cancelTokens.remove(tag);
      }
      if (errorCallback != null) {
        errorCallback(httpError);
      }
    } catch (e, _) {
      bool intercept =
          await formatError?.call(tag, NetCode.unknown, e.toString()) ?? false;
      if (intercept) {
        _cancelTokens.remove(tag);
      }
      if (errorCallback != null) {
        errorCallback(HttpError(NetCode.unknown, msg: e.toString()));
      }
    } finally {
      // 移除cancelToken
      _cancelTokens.remove(tag);
    }
  }

  /// 检查网络状态，无网络状态返回网络未连接
  Future<bool> _checkNetworkStatus() async {
    List<ConnectivityResult> connectivityResult =
        await Connectivity().checkConnectivity();
    if (connectivityResult.contains(ConnectivityResult.mobile) ||
        connectivityResult.contains(ConnectivityResult.wifi) ||
        connectivityResult.contains(ConnectivityResult.ethernet)) {
      return true;
    } else {
      return false;
    }
  }

  /// 例：path => /xxx/:user_id
  /// params => user_id=27
  /// 结果 => /xxx/27
  /// 请求结果处理
  /// [path] 网络请求地址
  /// [params] 网络请求参数
  String _restfulUrl(String path, Map<String, dynamic> params) {
    params.forEach((key, value) {
      if (path.contains(key)) {
        path = path.replaceAll(':$key', value.toString());
      }
    });
    return path;
  }

  /// 取消网络请求
  /// [tag] 标志
  void cancel(String tag) {
    if (_cancelTokens.containsKey(tag)) {
      var cancelToken = _cancelTokens[tag];
      if (!(cancelToken!.isCancelled)) {
        // 对应token不为null且未cancel
        cancelToken.cancel();
      }
      _cancelTokens.remove(tag);
    }
  }

  /// 是否已经存在请求
  /// [tag] 标志
  bool hasRequest(String tag) {
    return _cancelTokens.containsKey(tag);
  }
}
